查看原文
其他

Android底层:通熟易懂分析binder:1.binder准备工作

我te man 牛晓伟
2024-08-24


写binder的初衷


提起binder,应该会有很多人说,binder这都已经多么老的技术了,并且分析binder的文章是一搜一大堆,你这完全没必要写binder方面的文章啊!

我其实对于这种观点不以为然,说下我的理由吧:


  1. 对自己看过的,学过的binder知识需要有一个总结,这个总结是非常必要的,不信大家可以想想,若没有总结,以前学过的知识是不是已经忘记了很多,没有总结那你学过的知识就是零散的,不成系统。

  2. 即时binder很老了,依然会有很多人不了解,有可能你会说,了解它有啥用。我想说binder是android基石中的基石,android中到处都可以看到binder的身影(AMS,WMS等等),若了解了binder,你可以对android会有一个很深的认识,进程与进程之间的交互逻辑,进程与系统服务之间交互逻辑等等。

  3. Android系统架构是分为app,framework,native,linux kernel层,这四层之间到底是一个什么联系,当了解了binder(binder贯穿了这四层)后,您就会对这四层之间是怎么样协同工作来保证系统正常运行的。



疑惑点

在学习binder的过程中或多或少都会遇到一些疑惑点,设计者为什么这样来设计,我遇到了以下的疑惑点:


  1. 两个进程(client和server进程)之间通信,client进程拿到的是server进程的代理类(其实是一个handle),那这个handle是在什么时候生成的,生成规则是啥?

  2. app进程是怎么与驱动层建立一一对应关系的

  3. 驱动层中client是怎么样传递数据并且唤醒server的

  4. client进程调用server进程方法时是怎么样导致阻塞的

  5. 驱动层的 binder_proc, binder_thread和上层(非驱动层)的进程,线程是一个什么关系。binder_node, binder_ref, binder_nodes, binder_refs作用是啥?

  6. app进程是在啥时候open driver(打开驱动层)的

  7. ServiceManager进程起什么作用,ServiceManager存储的是系统服务的代理binder,还是真正的系统服务对象

  8. app进程与系统服务(如AMS)进行通信时,是每次都要经过ServiceManager去拿到系统服务的binder吗?

  9.  client进程与server进程通信是否要经过ServiceManager进程


通过阅读源码以及参考大牛的文章(比如gityuan,老罗)等,基本上把上面的疑惑点都解决了,因此希望能在这个系列文章中把这些疑惑给解决。同时也希望自己能从一个通熟易懂的角度来给大家分析binder,这也是我在文章的题目中要加 **通熟易懂** 的目的。

当然如果想通过一篇文章把binder讲解的很清楚是不可能的,因此这会是一个系列的文章。


android的源码是9.0.0_r3/

binder_driver层的源码需要单独去google官网下载,不要选择goldfish(模拟器)的版本,选择其他的版本。驱动层代码: binder.c。


首先我们从**binder的准备工作**开始这个系列文章。为啥要从准备工作讲起,因为我觉得追溯一个事物要从它的根源追起。



1.binder准备工作


binder在能进行进程通信之前,是需要做一些准备工作的,这些工作大致上有下面3个:


1. open binder driver

2. mmap

3. 启动binder主线程,接收数据


在讲准备工作之前,先讲下系统调用。



2.系统调用


app进程或系统进程是属于两个不同的进程,进程之间是没办法通信的,因此需要一个中间者来参与,这个中间者就是linux内核,如下图:



app进程或系统进程又被称为用户空间, linux内核被称为内核空间,

用户空间在调用内核空间程序的过程被称为系统调用(syscall)在进行系统调用时,用户空间会陷入内核态


陷入内核态简单理解就是:用户空间的线程暂停执行(处于中断状态),切换到内核线程执行内核程序,当内核程序执行完毕后,切换到用户空间,用户空间线程恢复执行;反之若内核程序处于阻塞状态,则用户空间的线程也一直处于中断状态。


binder用户空间调用到内核空间方法的对应关系是:

open-> binder_open,  ioctl->binder_ioctl,mmap->binder_mmap 等等。


系统调用可以说是binder的核心原理,内核空间是共享内存的。



3. open binder driver



第一个准备工作为啥是open binder driver呢?我们从代码上分析下都做了哪些事情,分析完后自然就能知道了,对应的类是**ProcessState**,这个类是一个单例的,因此一个进程中只存在一个实例,调用打开驱动的代码如下:


static int open_driver(const char *driver){ // 打开binder driver的关键方法,最终会调用driver层的binder_open方法, 返回的fd很关键,后面与driver的交互都需要带上它 int fd = open(driver, O_RDWR | O_CLOEXEC); if (fd >= 0) { int vers = 0; // 获取binder版本号 status_t result = ioctl(fd, BINDER_VERSION, &vers); if (result == -1) { ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); close(fd); fd = -1; } if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) { ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)! ioctl() return value: %d", vers, BINDER_CURRENT_PROTOCOL_VERSION, result); close(fd); fd = -1; } size_t maxThreads = DEFAULT_MAX_BINDER_THREADS; // 告诉driver层可以启动的最大的线程数 result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); if (result == -1) { ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); } } else { ALOGW("Opening '%s' failed: %s\n", driver, strerror(errno)); } return fd; }


调用open方法后会返回一个fd值,这个值很重要,后面的每次与driver通信都会用到它。


open方法最终会调到driver层的binder_open方法:


static int binder_open(struct inode *nodp, struct file *filp){ // 声明binder_proc,后续对它分配空间,初始化等操作 struct binder_proc *proc; 省略代码 ... proc = kzalloc(sizeof(*proc), GFP_KERNEL); if (proc == NULL) return -ENOMEM; get_task_struct(current); proc->tsk = current; proc->vma_vm_mm = current->mm; INIT_LIST_HEAD(&proc->todo); init_waitqueue_head(&proc->wait); 省略代码 ... hlist_add_head(&proc->proc_node, &binder_procs); proc->pid = current->group_leader->pid; INIT_LIST_HEAD(&proc->delivered_death); filp->private_data = proc; 省略代码 ... return 0; }


open binder driver这一个过程,做了几件很重要的事情:


  1. 在driver层对**binder_proc**(binder_proc记录了很多的信息,后续会着中介绍它)进行了初始化,对binder_proc中的todo队列等信息做了初始化。

  2. 把上层的pid(进程id)记录在了binder_proc中

  3. 把binder_proc赋值给了filp->private_data,filp是和返回给上层的fd值是存在一定关系的,通过fd是可以找到filp,进而找到当前进程的binder_proc。

  4. ProcessState把返回的fd存在内存中,以便与driver层进行通信

  5. 获取driver层的版本信息(ioctl获取)进行对比

  6. 通知driver层上层最多启动几个binder线程(ioctl通知)


open binder driver后,上层的进程在driver层就有了记录,后续上层进程就可以与driver层通信了(通信基本都是通过ioctl进行的)。



4.mmap(内存映射)


ProcessState::ProcessState(const char *driver){ 省略代码... // 打开驱动成功后 if (mDriverFD >= 0) { // 进行内存映射 // mmap the binder, providing a chunk of virtual address space to receive transactions. mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); if (mVMStart == MAP_FAILED) { // *sigh* ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str()); close(mDriverFD); mDriverFD = -1; mDriverName.clear(); } } 省略代码... }在ProcessState中,打开binder驱动后,紧接着做第二个准备工作mmap。对应driver层的方法是: binder_mmap    binder在性能方面能优于其他进程通信的其中一个因素是进程通信中的数据只拷贝一次,拷贝一次的重要原因就是mmap(内存映射),关于mmap的介绍网上有好多的介绍,这边就不赘述了。



5.进程与driver层通信



在讲启动binder主线程,接收数据内容之前,需要把一个基础的一直提到的进程与driver层通信内容讲清楚,这样非常有利于我们理解后面的内容。


不像socket通信,client和server端在连接建立后是可以互相发送消息的,binder进程与driver层通信原理是系统调用(syscall),通信的发起方只能是上层的进程。


5.1发送数据给driver层


主要是通过ioctl方法传递数据,调用过程是ioctl-->binder_ioctl(driver层方法),

ioctl(fd, cmd,数据)

ioctl的参数如上,fd不用说了就是打开binder驱动返回的值(用于查找当前进程在driver层的binder_proc),cmd的值有BINDER_WRITE_READ,BINDER_SET_MAX_THREADS等等,数据对应的结构体有binder_write_read等。


5.2driver层返回数据


有些调用是需要driver层返回数据的,比如cmd为BINDER_WRITE_READ的调用,driver层把需要返回的数据调用copy_to_user方法copy 用户空间中,当binder_ioctl方法执行完毕后,切换到用户空间线程,这时候从binder_write_read.read_buffer中取数据。


5.3 binder_write_read


上面讲完了上层进程与driver层的通信方式,为什么要说binder_write_read呢,因为binder_write_read是binder通信过程中使用最频繁的结构体,进程之间通信都用的是它,咱们先有一个初步的了解,后面还会在详细介绍,看下它的定义


struct binder_write_read { //write开头和传递给driver层的数据有关系 binder_size_t write_size; binder_size_t write_consumed; //write_buffer传递给driver层数据 binder_uintptr_t write_buffer; //read开头和driver层返回的数据有关系 binder_size_t read_size; binder_size_t read_consumed; //read_buffer driver层返回的数据 binder_uintptr_t read_buffer; };


那我们从代码流程上来看下是怎么样在上层进程与driver层传递binder_write_read的,我们只是先简单看下这一流程,在后面部分还会着中细说这一流程细节。这个流程主要是在IPCThreadState这个类中。

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)


writeTransactionData方法先把进程之间交互的关键数据写入**binder_transaction_data**这个结构体中,在把binder_transaction_data写入mOut,mOut的数据最终写入binder_write_read的write_buffer中,最终调用:


    status_t IPCThreadState::talkWithDriver(bool doReceive)


talkWithDriver方法从方法名就能看出是与driver层进行通信的,doReceive为true代表等待driver层返回数据,否则不等待。数据最终写入binder_write_read中,该方法中最终调用下面方法:


     ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)


ioctl最终调用到driver层的**binder_ioctl**方法


static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)


binder_ioctl方法中因为当前的cmd是BINDER_WRITE_READ,因此会执行binder_ioctl_write_read方法


static int binder_ioctl_write_read(struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread)


binder_ioctl_write_read方法中,binder_thread_write方法会读取上层传递的数据,binder_thread_read方法会根据情况返回给上层数据。


binder_thread_write非常的重要,比如进程通信完成通知driver层做收尾工作,回复数据给对方进程,以及driver层通知上层启动binder线程等等。先暂时简单的介绍下这方面内容,以便于我们能更好的理解后面的内容,在后面时我们还会详细讲解相关的内容。



6. 启动binder主线程,接收数据



6.1为什么启动binder主线程
为什么要启动binder主线程,拿socket通信来说,socket的client和server两端要想进行通信,server端必须先启动以等待client端连接进而通信。同理binder进程之间通信分为client进程和server进程的,那server进程需要监听client进程传递过来的数据,那这个监听行为就需要放在一个线程中,那这个线程就是binder主线程,binder主线程就是一个接收者。
6.2 启动binder主线程流程分析
那我们就从代码上分析下,分析的时候会用到刚刚提到的进程与driver层通信binder_write_read内容,
void ProcessState::startThreadPool() { AutoMutex _l(mLock); if (!mThreadPoolStarted) { mThreadPoolStarted = true; // true代表启动binder主线程 spawnPooledThread(true); } }


再来看下spawnPooledThread方法


void ProcessState::spawnPooledThread(bool isMain) { // binder主线程启动后执行 if (mThreadPoolStarted) { // binder线程的名字,格式: binder:pid_x(x代表第几个线程) String8 name = makeBinderThreadName(); ALOGV("Spawning new pooled thread, name=%s\n", name.string()); // new一个线程isMain是否是binder主线程 sp<Thread> t = new PoolThread(isMain); t->run(name.string()); } }

看下PoolThread类


class PoolThread : public Thread { public: explicit PoolThread(bool isMain) : mIsMain(isMain) { }
protected: virtual bool threadLoop() { // 线程启动后,执行IPCThreadState的方法 IPCThreadState::self()->joinThreadPool(mIsMain); return false; }
const bool mIsMain; };


启动线程后,最终调用IPCThreadState::self()->joinThreadPool(mIsMain)方法,上面的流程主要是在ProcessState类中,现在切换到IPCThreadState类中

void IPCThreadState::joinThreadPool(bool isMain) { // mOut的数据会发送给driver层,BC_ENTER_LOOPER:代表是binder主线程, //BC_REGISTER_LOOPER:代表是driver底发命令启动的binder线程 mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
status_t result; do { processPendingDerefs(); // 等待driver层传递的数据,进行处理,没数据则处于等待状态 // now get the next command to be processed, waiting if necessary result = getAndExecuteCommand();
省略代码...
// 若当前线程不再用于了并且是非主线程,则之间退出 // Let this thread exit the thread pool if it is no longer // needed and it is not the main process thread. if(result == TIMED_OUT && !isMain) { break; } } while (result != -ECONNREFUSED && result != -EBADF);
// binder线程退出了,需要通知drive层,以便做一些处理 mOut.writeInt32(BC_EXIT_LOOPER); // 传false代表通知driver层后,立马返回,不等待driver层数据 talkWithDriver(false); }

joinThreadPool()方法,若isMain为true代表binder主线程,则不会退出,基本会随着整个进程一直存在(除非发生错误)。在线程退出时,也是需要通知driver层的。着中看下getAndExecuteCommand()这个方法


status_t IPCThreadState::getAndExecuteCommand() { status_t result; int32_t cmd; // 发送数据给driver层,并且等待返回数据 result = talkWithDriver(); if (result >= NO_ERROR) { size_t IN = mIn.dataAvail(); if (IN < sizeof(int32_t)) return result; cmd = mIn.readInt32(); 省略代码...
//拿到cmd进行处理 result = executeCommand(cmd);
省略代码... }
return result; }


getAndExecuteCommand()方法会调用talkWithDriver()来发送数据给driver层,并且等待driver层的返回数据,调用executeCommand(cmd)方法会处理返回的cmd,咱们暂时不讨论这个过程,看下talkWithDriver()方法:


// doReceive默认值为true status_t IPCThreadState::talkWithDriver(bool doReceive) { if (mProcess->mDriverFD <= 0) { return -EBADF; } // 声明一个binder_write_read binder_write_read bwr;
// Is the read buffer empty? const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading // from data left in the input buffer and the caller // has requested to read the next data. const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail; bwr.write_buffer = (uintptr_t)mOut.data();
// doReceive && needRead都为true,代表会等待driver层的返回数据,没返回之前处于阻塞状态 // This is what we'll read. if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); // 返回数据最终从mIn中读取 bwr.read_buffer = (uintptr_t)mIn.data(); } else { bwr.read_size = 0; bwr.read_buffer = 0; }
省略代码...
bwr.write_consumed = 0; bwr.read_consumed = 0; status_t err; do { 省略代码... // 与driver层通信 if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; else err = -errno;
省略代码... } while (err == -EINTR);
省略代码...
if (err >= NO_ERROR) { if (bwr.write_consumed > 0) { if (bwr.write_consumed < mOut.dataSize()) mOut.remove(0, bwr.write_consumed); else { mOut.setDataSize(0); processPostWriteDerefs(); } } // 是否有返回数据 if (bwr.read_consumed > 0) { mIn.setDataSize(bwr.read_consumed); mIn.setDataPosition(0); } 省略代码...
return NO_ERROR; }
return err; }


talkWithDriver()方法中最终会调用ioctl与driver层通信,发送BC_ENTER_LOOPER给driver层,在进程与driver层通信binder_write_read 时讲到最终会走到driver层的binder_ioctl_write_read方法,binder_thread_write方法读数上层的数据(暂时不介绍),现在着中看下binder_thread_read方法


static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed, int non_block) { 省略代码...
retry: // wait_for_proc_work 为true,标明当前内核线程没有要处理的事务,因此即将进入等待状态 wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo);
省略代码...
if (wait_for_proc_work) { // 当前内核线程进入等待状态,等待新事务加入被唤醒 if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED))) { binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n", proc->pid, thread->pid, thread->looper); wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); } binder_set_nice(proc->default_priority); if (non_block) { if (!binder_has_proc_work(proc, thread)) ret = -EAGAIN; } else ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread)); } else { 省略代码... }
省略代码... // 说明被唤醒了 thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
if (ret) return ret;
while (1) { // 下面省略的代码取出事务开始执行 省略代码... }
done:
// 下面省略的代码根据条件来通知上层进程是否开启新的binder线程 省略代码... return 0;}


binder_thread_read方法,若当前内核线程没有事务要处理,则内核线程进入等待状态,因此会导致上层进程对应的binder线程也进入等待状态,若有事务了则内核线程会被唤醒,紧接着处理事务(这部分内容我们会在后面介绍)。


到此为止启动binder主线程的流程基本分析完成,我们来总结下:


  1. 首先会在当前进程启动binder主线程,主线程生命周期和进程是一致的。

  2. 进程之间毕竟是隔离的,即时启动了binder主线程,主线程在没有binder驱动的协调下也不会收到别的进程的数据,因此binder主线程启动完毕后会通知driver层,driver层的对应内核线程进入等待状态,进而导致上层的binder主线程进入等待状态。

  3. driver层的内核线程收到事务后,处理后把相关的数据返回给上层进程中的对应binder线程。

  4. binder线程分为主线程和普通线程,普通线程执行完毕后一般会销毁



7. binder准备工作调用时机




至此binder的准备工作已经完毕了,但是还有一个很重要的工作没做,那就是binder的准备工作什么时候被调用,具体调用是在app_main.cpp文件中的AppRuntime类中

virtual void onZygoteInit(){ sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); }


onZygoteInit方法中,ProcessState::self() ProcessState构造函数开始初始化,初始化时会open binder drivermmap,这些工作做完后执行proc->startThreadPool(),这个方法会启动binder主线程,接收数据


AppRuntime的onZygoteInit方法是在app进程zygote之后立马就调用的,因此每个app进程只要被zygote出来后,都会立马把binder的准备工作做好。



8. 总结


下面我用一张图来总结下本篇的内容


open binder driver后上层进程就可以与driver层通信了,启动binder主线程当前的进程就可以作为binder进程通信的server端了,可以接收client进程的数据了。


不管是server进程还是client进程,在driver层都有对应的内核线程处于中断状态,在等待事务(binder_transcation后面会着中介绍),为什么client进程也需要一个等待的内核线程呢?这是因为client在进行进程通信时,只要传递给别的进程的数据中包含binder(此binder是BBinder,非BpBinder代理类)对象时,client进程随时都会变为binder服务的提供者。



继续滑动看下一个
牛晓伟
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存